"""
Copyright 2007-2016 VMware, Inc.  All rights reserved. -- VMware Confidential

Program to interact with the VMware Installer Service.
"""

import os
import sys
import traceback
import warnings

from functools import partial
from lxml import etree
from optparse import OptionParser, OptionGroup, SUPPRESS_HELP

from vmis import VERSION, BUILD_NUMBER, ui
from vmis.db import DatabaseError, MultipleInstallersError, Load
from vmis.core.common import SYSTEM_BOOTSTRAP, SYSTEM_DATABASE, ParseExceptionTuple
from vmis.core.component import ComponentTypes
from vmis.core.errors import DowngradeError
from vmis.core.files import File, ConfigFile
from vmis.util import Format
from vmis.util.log import getLog
from vmis.util.path import path
from vmis.util.shell import run
from vmis.ui import MessageTypes
from vmis.core import errors

DB_COLUMN = 0
ROOT_COLUMN = 1

# Set up restrictions on the DB lock and root access here
#
#                 name                  DB    root
restrictions = {'setSetting':         [True, True],
                'deleteSetting':      [True, True],
                'registerFile':       [False, True],

                'installBundle':      [True, True],
                'installComponent':   [True, True],
                'uninstallProduct':   [True, True],
                'uninstallComponent': [True, True],
                'resolveSystem':      [True, True],

                'dumpDB':             [False, False],
                'getSetting':         [False, False],
                'listProducts':       [False, False],
                'listComponents':     [False, False],
                'listFiles':          [False, False],
                'findFile':           [False, False]}

# Settings that don't exit after being run.
nonExitingOptions = ['setSetting',
                     'deleteSetting',
                     'registerFile']

def Exit(text, errorCode=errors.EXIT_ERROR):
   """ Prints an error message to stderr if provided and exits """
   if text:
      try:
         getattr(ui, 'ShowMessage')
         ui.ShowMessage(MessageTypes.ERROR, text)
      except Exception:
         # Otherwise print message to stdout.
         print >> sys.stderr, text

   sys.stdout.flush()
   sys.stderr.flush()
   os._exit(errorCode)

log = getLog('vmis.cli')

def _showwarningDecorate(orishowwarning):
   def _wrapper(message, category, filename, lineno, file=None):
      """ Redirect Python warning messages to log file for Gtk+ """

      # Gtk+ likes to show warnings when we're simply trying to
      # initialize it to see if it is in a runnable state.  Normally you
      # would add a filter using gtk.Warning as the category but simply
      # importing gtk will result in a warning.  So, we must check using
      # the class name instead.
      if category.__name__ == 'GtkWarning':
        log.warning(warnings.formatwarning(message, category, filename, lineno))
      else:
        orishowwarning(message, category, filename, lineno, file)
   return _wrapper;

# Don't replace this with "functools.partial". Otherwise, we may encounter
# "TypeError: warnings.showwarning() must be set to a function or method"
# See bug 1698942 #7.
warnings.showwarning = _showwarningDecorate(warnings.showwarning)

def _getCompId(database, name):
   """ Returns the component id with the given name if exists, otherwise exits """
   return database.components.FindByName(name) or -1

def _checkRestrictions(options, column):
   """
   Checks which options have been selected and returns whether access
   must be restricted.

   @param options: The selected command line options
   @param column: The column of the restriction table to check
   """
   lock = False

   # Check for command line options.  If any are True, set it to True
   for opt in restrictions:
      if getattr(options, opt):
         lock = restrictions[opt][column] or lock

   return lock

def _isRootRequired(options):
   """
   Check if root access is required to continue.
   """
   return _checkRestrictions(options, ROOT_COLUMN)

def _isLockRequired(options):
   """
   Check if the database must be locked to continue.
   """
   return _checkRestrictions(options, DB_COLUMN)

# XXX: Borrowed from the WS component.  Once library
# files are created for them, share this code.
def _parseConfigurationFile(configPath):
   """
   Get information from the plugin config file and return
   a dict of its configuration.  This functions parses out
   lines of the form
      X=Y
   and stores them as:
      features[X] = Y

   @param configPath: Path to the configuration file
   @return: a dict of features.  See above.
   """
   configPath = path(configPath)

   features = {}
   fileObj = None
   try:
      fileObj = configPath.open()
      for line in fileObj:
         line = line.strip()
         try:
            key, val = line.split('=')
            features[key] = val
         except ValueError:
            # Not all lines are splittable by =
            pass
   finally:
      fileObj and fileObj.close()

   return features

def MigrateDB():
   """
   Migrate from an old version of the database

   @raises IOError: if the bootstrap can't be opened
   @raises OSError: if the installer isn't found
   """
   import tempfile
   features = _parseConfigurationFile(SYSTEM_BOOTSTRAP)
   systemInstaller = features.get('VMWARE_INSTALLER')
   if systemInstaller[0] == '"':
      systemInstaller = systemInstaller.split('"')[1]
   systemInstaller = systemInstaller + '/vmware-installer'
   try:
      xmlFd, xmlFile = tempfile.mkstemp(suffix='.xml')
      sqlFd, sqlFile = tempfile.mkstemp(suffix='.sqlite')
      db = None
      # mkstemp returns the fd to guarantee security--however, nothing we use
      # accepts this, so we just close it
      os.close(sqlFd)
      os.close(xmlFd)
      # Get the old database as XMLz
      run(systemInstaller, '--dumpDB', xmlFile)
      from vmis import DATABASE_PATH
      from vmis.db import Database
      db = Database(path(sqlFile), True)
      tree = etree.ElementTree(file=xmlFile)
      db.ImportXML(tree)
      db.Close()
      os.rename(sqlFile, DATABASE_PATH)
   except:
      # Clean up on error
      if db:
         db.Close()
      os.remove(sqlFile)
      raise
   finally:
      os.remove(xmlFile)

def UseNewestInstaller():
   """
   Check the state of the system and verify that this installer is the
   newest.  If it is not, locate the newer installer and run it, passing
   our arguments to it.
   """
   # Bootstrap file will ALWAYS have the newest installer.
   bootstrap = SYSTEM_BOOTSTRAP
   try:
      features = _parseConfigurationFile(bootstrap)
      # VMISVERSION was Introduced in VMIS 2.0 as a replacement to VERSION
      # to counter a downgrading bug.  If we don't find VMISVERSION, we
      # know we're newer, so it's okay not to look at the VERSION.
      systemVersion = features.get('VMISVERSION')
      systemInstaller = features.get('VMWARE_INSTALLER')
      systemBuildNum = features.get('VMISBUILDNUM')
   except IOError:
      # Error is acceptable.  It means no bootstrap file was found and
      # the system is theoretically clean.  If bootstrap is missing,
      # we can't trust any database file still leftover, so delete it
      # if it exists and continue with installation.
      dbase = SYSTEM_DATABASE
      if dbase.exists():
         dbase.remove(ignore_errors=True);
         log.error('Bootstrap file was missing, but database file exists.  '
                   'Installer system state is corrupted.  Removing '
                   '%s and starting with an empty installer database.'
                   % SYSTEM_DATABASE)
      return
   if not systemVersion or not systemBuildNum:
      # If there was no version or build number found on the system,
      # we know our version is newer.
      return

   # Clean up any quotes.
   if systemVersion[0] == '"':
      systemVersion = systemVersion.split('"')[1]
   if systemBuildNum[0] == '"':
      systemBuildNum = systemBuildNum.split('"')[1]

   if systemBuildNum == "00000" or BUILD_NUMBER == 0:
      # Build 0 is a developer build.  Always use the current installer.
      return

   from vmis.core.version import Version, LongVersion
   sysVer = LongVersion(systemVersion, buildNumber=systemBuildNum)
   myVer = LongVersion(VERSION, buildNumber=BUILD_NUMBER)

   log.info('System installer version is: %s' % str(sysVer))
   log.info('Running installer version is: %s' % str(myVer))

   # If the system version is higher than this version, we want
   # to use it instead.  Re-run the system installer with our
   # arguments.
   if sysVer > myVer:
      log.info('Running newer system installer.')
      # Cleanup quotes
      if systemInstaller[0] == '"':
         systemInstaller = systemInstaller.split('"')[1]
      # Replace the first argument to this installer with the
      # path to the aready installed installer.
      args = sys.argv[:]
      systemInstaller = path(systemInstaller + '/vmware-installer')
      # Only try to use the new installer if it exists and if it's executable
      # Otherwise fall back on the current installer.
      if systemInstaller.exists() and systemInstaller.isexe():
         args[0] = systemInstaller

         # The environment has to be set up correctly before running
         os.environ['VMWARE_BOOTSTRAP'] = SYSTEM_BOOTSTRAP
         # Now call the installer, passing along the original
         # arguments.  We don't want anything fancy, no output
         # collection or anything, just to run the system
         # installer, so use os.system
         exitCode = os.execv(systemInstaller, args)
         # Execution ends here.
         Exit('', exitCode)

def main(options):
   """
   Processes options and gets things going

   @param options: already parsed options
   """

   # Options
   opts = {}
   opts['level'] = options.questionLevel
   opts['ignoreErrors'] = options.ignoreErrors
   opts['eulasAgreed'] = options.eulasAgreed

   # The ui initialization code below must be called BEFORE the import
   # for the transaction class. The transaction class relies on variables
   # that the ui initializes for itself to initialize correctly.
   try:
      ui.Initialize(options.ui)
   except Exception, e:
      log.exception('UI Initialization failed.')
      Exit('User interface initialization failed.  Exiting.  Check the log for details.',
           errors.EXIT_ERROR)

   from vmis.core import transaction as txn

   # Extract a bundle.  This option MUST come before database load as
   # it is the only option that can be run from both a bundle and from
   # the installer without need for root or DB access.  It will exit
   # immediately after execution.
   if options.extractBundle:
      # We need a repository, but no database, so pass in None
      from vmis.core.common import SetRepository
      SetRepository(None)
      txn.Extract(options.installComponent, options.installBundle,
                  options.extractBundle)
      Exit('', errors.EXIT_OK)

   # Check if we need root access, and if so, if we are root
   if _isRootRequired(options) and os.getuid() != 0:
      Exit('root access is required for the operations you have chosen.',
           errors.EXIT_ROOT_REQUIRED)

   # Check to be sure we are the newest installer.  If we are not, then run
   # the version installed on the system.
   UseNewestInstaller()

   lockRequired = _isLockRequired(options)

   try:
      # Database needs to be explicitly loaded so that it doesn't try to
      # get loaded during component building.
      Load(lockRequired)

      # Now import the database and set up the repository
      from vmis.db import database
      from vmis.core.common import SetRepository
      SetRepository(database) # XXX: I don't like that it is necessary
                              #  to set the DB for the repository. It
                              #  used to be set when the global database
                              #  was created.
   except MultipleInstallersError:
      Exit('Another installation is already in progress.  Complete the '
           'currently running installation before running this one.',
           errors.EXIT_MULTIPLE_INSTALLERS)
   except DatabaseError:
      Exit('Unable to load the installation database.', errors.EXIT_ERROR)

   # Database dump
   if options.dumpDB:
      XMLroot = database.DumpToXML()
      text = etree.tostring(XMLroot, pretty_print=True)
      dbFile = path(options.dumpDB)
      dbFile.write_bytes(text)
      Exit('', errors.EXIT_OK)

   # Non-exiting DB writing options
   # Settings
   for comp, key, val in options.setSetting or []:
      database.config.Set(comp, key, val)
      database.Commit()

   if options.prefix:
      database.config.Set('vmware-installer', 'prefix', options.prefix)
      database.Commit()

   if options.deleteSetting:
      comp, key = options.deleteSetting

      if database.config.Contains(comp, key):
         database.config.Remove(comp, key)
         database.Commit()
      else:
         Exit(u'Key %s does not exist' % key, errors.EXIT_ERROR)

   # Register a file with the DB
   if options.registerFile:
      name, fileType, filePath = options.registerFile

      filePath = path(filePath)
      cid = database.components.FindByName(name)

      if not cid:
         Exit(u'The component %s does not exist.' % name, errors.EXIT_ERROR)

      if fileType not in ('config', 'regular'):
         Exit(u'The file type must be either config or regular.', errors.EXIT_ERROR)

      if fileType == 'config':
         fileType = ConfigFile.id
      else:
         fileType = File.id

      if not filePath.exists():
         Exit(u'The path %s does not exist.' % filePath, errors.EXIT_ERROR)

      database.files.Add(path=filePath, mtime=int(filePath.mtime),
                        fileType=fileType, component=cid, replace=True)
      database.Commit()

   # Read-only options
   # Print a setting
   if options.getSetting:
      comp, key = options.getSetting
      setting = database.config.Get(comp, key)
      if setting:
         print setting
         Exit('', errors.EXIT_OK)
      else:
         Exit(u'Key %s does not exist' % key, errors.EXIT_ERROR)

   # List Products
   if options.listProducts:
      # Get field widths to be sure our fields fit our text
      prodNameWidth  = 20;  # Arbitrary minimums
      prodVerWidth   = 20;
      for uid in database.components.GetComponents():
         if database.components.GetType(uid) == ComponentTypes.PRODUCT:
            wid = len(database.components.GetName(uid))
            if wid > prodNameWidth:
               prodNameWidth = wid
         wid = len('%s.%s' % (database.components.GetVersion(uid),
                              database.components.GetBuildNumber(uid)))
         if wid > prodVerWidth:
            prodVerWidth = wid

      # Now set up our format string with these widths
      FORMAT = u'%-' + str(prodNameWidth) + 's %-' + str(prodVerWidth) + 's'
      print FORMAT % (u'Product Name', u'Product Version')
      print FORMAT % ('=' * prodNameWidth, '=' * prodVerWidth)

      for uid in database.components.GetComponents():
         if database.components.GetType(uid) == ComponentTypes.PRODUCT:
            # Append the build number when displaying the version
            version = '%s.%s' % (database.components.GetVersion(uid),
                                 database.components.GetBuildNumber(uid))
            print FORMAT % (database.components.GetName(uid), version)
      Exit('', errors.EXIT_OK)

   # List Components
   if options.listComponents:
      # Get field widths to be sure our fields fit our text
      compNameWidth  = 20;  # Arbitrary minimums
      compLnameWidth = 40;
      compVerWidth   = 20;
      for uid in database.components.GetComponents():
         wid = len(database.components.GetName(uid))
         if wid > compNameWidth:
            compNameWidth = wid
         wid = len(database.components.GetLongName(uid))
         if wid > compLnameWidth:
            compLnameWidth = wid
         wid = len('%s.%s' % (database.components.GetVersion(uid),
                              database.components.GetBuildNumber(uid)))
         if wid > compVerWidth:
            compVerWidth = wid

      # Now set up our format string with these widths
      FORMAT = u'%-' + str(compNameWidth) + 's %-' + str(compLnameWidth) + \
               's %-' + str(compVerWidth) + 's'
      print FORMAT % (u'Component Name', u'Component Long Name', u'Component Version')
      print FORMAT % ('=' * compNameWidth, '=' * compLnameWidth, '=' * compVerWidth)

      for uid in database.components.GetComponents():
         # Append the build number when displaying the version
         version = '%s.%s' % (database.components.GetVersion(uid),
                              database.components.GetBuildNumber(uid))
         print FORMAT % (database.components.GetName(uid),
                         database.components.GetLongName(uid),
                         version)
      Exit('', errors.EXIT_OK)

   # List files
   if options.listFiles:
      uid = _getCompId(database, options.listFiles)
      if uid == -1:
         errorMsg = '%s is not installed\n\n' % options.listFiles
         errorMsg += 'Available components are:\n\n'
         for uid in database.components.GetComponents():
            errorMsg += '  %s\n' % database.components.GetName(uid)
         ui.ShowMessage(MessageTypes.ERROR, errorMsg, useWrapper=False)
         Exit('', errors.EXIT_ERROR)
      for f in database.components.GetFiles(uid):
         print database.files.GetPath(f)
      Exit('', errors.EXIT_OK)

   # Find a file
   if options.findFile:
      # Get field widths to be sure our fields fit our text
      compNameWidth = 20;  # Arbitrary minimums
      fileNameWidth = 30;
      for fid in database.files.FindByGlob(options.findFile):
         name = database.components.GetName(database.files.GetComponent(fid))
         filePath = database.files.GetPath(fid)

         if len(name) > compNameWidth:
            compNameWidth = len(name)
         if len(filePath) > fileNameWidth:
            fileNameWidth = len(filePath)

      FORMAT = u'%-' + str(compNameWidth) + 's %-s'
      print FORMAT % (u'Component', u'File')
      print FORMAT % ('='* compNameWidth, '=' * fileNameWidth)

      for fid in database.files.FindByGlob(options.findFile):
         name = database.components.GetName(database.files.GetComponent(fid))
         filePath = database.files.GetPath(fid)
         print FORMAT % (name, filePath)
      Exit('', errors.EXIT_OK)

   # Options that require writing to the DB and thus root access.
   # Core
   elif options.uninstallComponent:
      # Check if components exist and find IDs
      compIDs = []
      badComponent = None
      for comp in options.uninstallComponent:
         uid = _getCompId(database, comp)
         if uid == -1:
            badComponent = comp
         compIDs.append(uid)
      if badComponent is not None:
         errorMsg = ''
         if options.uninstallComponent != ['']:
            errorMsg = '%s is not an installed component.\n' % badComponent
         errorMsg += 'Available components are:\n\n'
         for uid in database.components.GetComponents():
            errorMsg += '  %s\n' % database.components.GetName(uid)
         ui.ShowMessage(MessageTypes.ERROR, errorMsg, useWrapper=False)
         Exit('', errors.EXIT_ERROR)
      txn.UninstallComponent(compIDs, opts)
   elif options.uninstallProduct:
      # Check if products exists and find IDs
      compIDs = []
      badComponent = None
      for comp in options.uninstallProduct:
         uid = _getCompId(database, comp);
         if uid == -1:
            badComponent = comp
         compIDs.append(uid)
      if badComponent is not None:
         errorMsg = ''
         if options.uninstallProduct != ['']:
            errorMsg = '%s is not an installed product.\n' % badComponent
         errorMsg += 'Available products are:\n\n'
         for uid in database.components.GetComponents():
            if database.components.GetType(uid) == ComponentTypes.PRODUCT:
               errorMsg += '  %s\n' % database.components.GetName(uid)
         ui.ShowMessage(MessageTypes.ERROR, errorMsg, useWrapper=False)
         Exit('', errors.EXIT_ERROR)
      txn.UninstallProduct(compIDs, opts)
   elif options.resolveSystem:
      txn.ResolveSystem(opts)
   elif options.installBundle or options.installComponent:
      txn.Install(options.installComponent, options.installBundle, opts, database)
   else:
      for opt in nonExitingOptions:
         if getattr(options, opt):
            # If any of these were found, do not print help and
            # exit normally
            Exit('', errors.EXIT_OK)
      parser.print_help()
      Exit('', errors.EXIT_ERROR)

   # If installation completed successfully, execution will pick up here again.
   # Check if the deleteInstallComponents flag was set.  If so, delete all components
   # listed on the command line.
   if options.deleteInstallComponents:
      for comp in options.installComponent:
         component = path.path(comp)
         log.info('--delete-install-components passed in, deleting component path: %s' % component)
         try:
            component.remove()
         except Exception, e:
            log.warn('Error removing component %s.' % component)
         # Try to remove the component's directory as cleanup.
         try:
            component.dirname().rmdir()
            log.info('--delete-install-components passed in, removed component directory: %s' % component.dirname())
         except OSError:
            # If it's not empty, we don't care.
            pass
      # Now delete the bundle if it's been set and remove its directory if it's empty.
      if options.installBundle:
         bundle = path.path(options.installBundle)
         log.info('--delete-install-components passed in, deleting bundle path: %s' % bundle)
         try:
            bundle.remove()
         except Exception, e:
            log.warn('Error removing bundle %s.' % bundle)
         # Try to remove the bundle's directory as cleanup.
         try:
            bundle.dirname().rmdir()
            log.info('--delete-install-components passed in, removed bundle directory: %s' % bundle.dirname())
         except OSError:
            # If it's not empty, we don't care.
            pass

def parse():
   """
   Parses command line options

   @returns: 3-tuple of (parser, options, args)
   """
   parser = OptionParser(version=VERSION, description='VMware Installer',
                         prog='vmware-installer')

   settingsGroup = OptionGroup(parser=parser, title='Settings',
                               description='Set and retrieve settings')
   coreGroup = OptionGroup(parser=parser, title='Manage',
                           description='Install or uninstall products')
   metaGroup = OptionGroup(parser=parser, title='Information',
                           description='Look up information on installed products')
   optionsGroup = OptionGroup(parser=parser, title='Options')

   parser.add_option_group(coreGroup)
   parser.add_option_group(metaGroup)
   parser.add_option_group(settingsGroup)
   parser.add_option_group(optionsGroup)

   settingsGroup.add_option('-g', '--get-setting', dest='getSetting', nargs=2,
                            help='Get setting', metavar='COMPONENT KEY')
   settingsGroup.add_option('-s', '--set-setting', dest='setSetting', nargs=3, action='append',
                            help='Set setting', metavar='COMPONENT KEY VALUE')
   settingsGroup.add_option('-d', '--delete-setting', dest='deleteSetting', nargs=2,
                            help='Delete setting', metavar='COMPONENT KEY')

   coreGroup.add_option('-i', '--install-bundle', metavar='FILE', dest='installBundle',
                        help='Install bundle from FILE')
   coreGroup.add_option('--install-component', dest='installComponent',
                        action='append', metavar='FILE', help='Install a component')
   coreGroup.add_option('--uninstall-component', metavar='NAME', action='append',
                        dest='uninstallComponent', help='Force uninstallation of a component')
   coreGroup.add_option('-u', '--uninstall-product', metavar='NAME', action='append',
                        dest='uninstallProduct', help='Uninstall a product')
   coreGroup.add_option('-r', '--resolve-system', dest='resolveSystem', action='store_true',
                        help='Force the system to resolve the current state')
   coreGroup.add_option('--register-file', dest='registerFile', nargs=3,
                        help='Register a file in the database',
                        metavar='COMPONENT_NAME (config|regular) FILE')
   coreGroup.add_option('-x', '--extract', metavar='DIR', dest='extractBundle',
                        help='Extract the contents of the bundle into DIR')
   coreGroup.add_option('--dumpDB', metavar='FILE', dest='dumpDB',
                        help=SUPPRESS_HELP)
   coreGroup.add_option('--delete-install-components', dest='deleteInstallComponents',
                        action='store_const', const='yes', help=SUPPRESS_HELP)

   coreGroup.add_option('-p', '--prefix', metavar='DIR', dest='prefix',
                        help='Set a custom install location')
   metaGroup.add_option('-l', '--list-products', dest='listProducts', action='store_true',
                        help='List installed products')
   metaGroup.add_option('-t', '--list-components', dest='listComponents', action='store_true',
                        help='List the installed components')
   metaGroup.add_option('-L', '--list-files', metavar='COMPONENT', dest='listFiles',
                        help='List files for a given component')
   metaGroup.add_option('-S', '--find-file', metavar='FILE', dest='findFile',
                        help='List components and files matching the given pattern')

   optionsGroup.add_option('--gtk', dest='ui', action='store_const', const='gtk',
                           help='Use the Gtk+ UI (Default)')
   optionsGroup.add_option('--console', dest='ui', action='store_const', const='console',
                           help='Use the console UI')

   optionsGroup.add_option('--custom', dest='questionLevel', action='store_const', const='custom',
                           help='Allow customization of the install, including file locations.')
   optionsGroup.add_option('--regular', dest='questionLevel', action='store_const', const='regular',
                           help='Displays questions that have no good defaults (Default)')
   optionsGroup.add_option('--required', dest='questionLevel', action='store_const', const='required',
                           help='Displays only questions absolutely required')

   optionsGroup.add_option('-I', '--ignore-errors', dest='ignoreErrors', action='store_true', default=False,
                           help='Ignore component script errors')

   optionsGroup.add_option('--eulas-agreed', dest='eulasAgreed', action='store_true', default=False,
                           help='Agree to the EULA')

   parser.set_defaults(ui='gtk', questionLevel='regular')

   options, args = parser.parse_args()
   return parser, options, args

# Useful as a debugging entry point.
if os.environ.get('VMIS_TRACE'):
   import pdb
   pdb.set_trace()

# Set umask so that normal users will get read access to files we
# create without explicit permissions.
os.umask(022)

parser, options, args = parse()

# Begin logging:
log.info('')
log.info('')
log.info('Installer running.')
log.info('Command Line Arguments:')
log.info(sys.argv)

try:
   main(options)
# Catch errors that we specifically need to check for.
except DowngradeError, e:
   Exit(str(e), errorCode=errors.EXIT_DOWNGRADE_ERROR)
except KeyboardInterrupt:
   log.exception('User pressed ctrl-C:')
   Exit('', errors.EXIT_CANCELLED)
except Exception, e:
   # Now check to see if the raised error is an error type defined in
   # the installer.  If it is, it should be formatted in a way that can
   # be presented to the user.
   # XXX: Sanitize our Exception error messages.  Make sure they're good for
   # user consumption.

   # Parse out the exception type.
   excepInfo = sys.exc_info()
   typ = ParseExceptionTuple(excepInfo)

   # Check if this is a known error from core/errors.py.  If it is, then
   # it's safe to present this error to the user.
   errorIsUserSafe = typ in errors.__dict__

   if errorIsUserSafe:
      # If this is an exception that has made its way up the component chain,
      # it will have a 'VMIS:' prepended to it to suppress logging.  Remove it
      # before displaying the message.
      st = str(e)
      arr = st.split('VMIS:')
      e = arr[-1]
      Exit(str(e), errorCode=errors.EXIT_ERROR)
   else:
      # Otherwise present a generic exception message and remind the user where
      # the log can be found.
      Exit('An unknown error occurred during installation.  Please '
           'check the installation log at /var/log/vmware-installer '
           'for more information.\n', errors.EXIT_ERROR)
